package mvcframework.servlet;

import mvcframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author gyg
 * @version 1.0
 * @date 2020/8/27 10:21
 * @Description:
 */
public class GygDispatcherServlet extends HttpServlet {


    private static final String LOCATION = "contextConfigLocation";


    private Properties properties = new Properties();

    // ioc容器  存储单例
    private Map<String, Object> ioc = new HashMap<>();

    private List<String> classNames = new ArrayList<>();

    private List<Handler> handlerMapping = new ArrayList<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req,resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        try {

            Handler handler = getHandler(req);
            if(handler == null){
                resp.getWriter().write("404 Not Fount");
                return;
            }
            Class<?>[] parameterTypes = handler.method.getParameterTypes();
            Object [] paramValues = new Object[parameterTypes.length];
            Map<String, String[]> params = req.getParameterMap();
            for (Map.Entry<String, String[]> param : params.entrySet()) {
                String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", "");
                if(handler.paramIndexMapping.containsKey(param.getKey())){
                    Integer index = handler.paramIndexMapping.get(param.getKey());
                    paramValues[index] = convert(parameterTypes[index],value);
                }
            }
            Integer indexReq = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[indexReq]= req;
            Integer indexResp = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[indexResp] = resp;

            handler.method.invoke(handler.controller,paramValues);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private Object convert(Class<?> type,String value){
        if(type == Integer.class){
            return Integer.valueOf(value);
        }
        return value;
    }

    private Handler getHandler(HttpServletRequest request){
        if(handlerMapping.isEmpty()){return null;}
        String url = request.getRequestURI();
        String contextPath = request.getContextPath();
        url = url.replace(contextPath,"/").replaceAll("/+","/");
        for(Handler handler : handlerMapping){
            try {
                Matcher matcher = handler.pattern.matcher(url);
                if(matcher.matches()){
                    return handler;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1、加载配置文件
        doLoadConfig(config.getInitParameter(LOCATION));

        // 2、扫描相关类
        doScanner(properties.getProperty("scanPackage"));

        // 3、初始化所以相关类的示例，保存到容器
        doInstance();

        // 4、依赖注入
        doAutowired();

        // 5、构造HandlerMapping
        initHandlerMapping();

        System.out.println("gyg mvc framework init end");
    }

    private void initHandlerMapping() {
        if(ioc.size() == 0 ){return;}
        else {
            for (Map.Entry<String, Object> entry : ioc.entrySet()){
                Class<?> clazz = entry.getValue().getClass();
                if(!clazz.isAnnotationPresent(GygController.class)){
                    continue;
                }
                String url = "";
                //获取类上面的url
                if(clazz.isAnnotationPresent(GygRequestMapping.class)){
                    GygRequestMapping requestMapping = clazz.getAnnotation(GygRequestMapping.class);
                    url = requestMapping.value();
                }

                //获取方法上面的URL
                Method[] methods = clazz.getMethods();
                for(Method method : methods){
                    if(!method.isAnnotationPresent(GygRequestMapping.class)){continue;};
                    GygRequestMapping requestMapping = method.getAnnotation(GygRequestMapping.class);
                    String methodUrl = requestMapping.value();
                    url = "/" + url + "/" +methodUrl;
                    String reg = url .replaceAll("/+","/");
                    Pattern pattern = Pattern.compile(reg);
                    handlerMapping.add(new Handler(pattern,entry.getValue(),method));
                }

            }
        }

    }

    private void doAutowired() {
        if(ioc.size() == 0){
            return;
        } else {
            for(Map.Entry<String, Object> entry : ioc.entrySet()){
                Field[] fields = entry.getValue().getClass().getDeclaredFields();
                for (Field filed: fields) {
                    if(!filed.isAnnotationPresent(GygAutowired.class)){continue;}
                    GygAutowired autowired = filed.getAnnotation(GygAutowired.class);
                    String beanName = autowired.value().trim();
                    if("".equals(beanName)){
                        beanName = filed.getType().getName();
                    }
                    filed.setAccessible(true);
                    try {
                        filed.set(entry.getValue(),ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                }

            }
        }
    }

    private void doInstance() {
        if(classNames.size() == 0){
            return;
        } else {
            try {
                for(String className : classNames){
                    Class<?> clazz = Class.forName(className);
                    if(clazz.isAnnotationPresent(GygController.class)){
                        String beanName = lowerFirst(clazz.getSimpleName());
                        ioc.put(beanName,clazz.newInstance());
                    } else if (clazz.isAnnotationPresent(GygService.class)){
                        GygService gygService = clazz.getAnnotation(GygService.class);
                        String beanName = gygService.value();
                        if (!"".equals(beanName.trim())) {
                            ioc.put(beanName,clazz.newInstance());
                        }
                        Class<?>[] interfaces = clazz.getInterfaces();
                        for(Class<?> calss : interfaces){
                            ioc.put(calss.getName(),clazz.newInstance());
                        }
                    }
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    private String lowerFirst(String str){
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file: dir.listFiles()) {
            if(file.isDirectory()){
                doScanner(scanPackage + "." + file.getName());
            } else {
                classNames.add(scanPackage + "." + file.getName().replace(".class","").trim());
            }
        }
    }

    private void doLoadConfig(String location) {
        InputStream is = null;
        try {
            is = this.getClass().getClassLoader().getResourceAsStream(location);
            properties.load(is);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private class Handler{

        //保存方法对应的示例
        protected Object controller;
        //保存映射的方法
        protected Method method;
        protected Pattern pattern;
        //参数顺序
        private Map<String, Integer> paramIndexMapping;

        public Handler(Pattern pattern,Object controller,Method method){
            this.pattern = pattern;
            this.controller = controller;
            this.method = method;
            paramIndexMapping = new HashMap<>();
            putParamIndexMapping(method);
        }
        
        private void putParamIndexMapping(Method method){
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for(int i = 0; i<parameterAnnotations.length; i++){
                for(Annotation an : parameterAnnotations[i]){
                    if(an instanceof GygRequestParam){
                        String paramName = ((GygRequestParam) an).value();
                        if(!"".equals(paramName)){
                            paramIndexMapping.put(paramName,i);
                        }
                    }
                }
            }
            Class<?>[] parameterTypes = method.getParameterTypes();
            for(int i = 0; i<parameterTypes.length; i++){
                Class<?> type = parameterTypes[i];
                if(type == HttpServletRequest.class || type == HttpServletResponse.class){
                    paramIndexMapping.put(type.getName(),i);
                }
            }
        }

    }

}
